sp_BlitzFirst: add Top 5 server/login/app breakdown to High Number of Connections#3921
Conversation
… Connections (#3903) When CheckID 49 fires, the Details column now includes three ranked sections (Top 5 Servers, Top 5 Logins, Top 5 Apps) showing connection counts plus the most-recent and oldest query finish ages for each. Operators can quickly spot whether connection pressure is concentrated in one app server or login vs. spread across the fleet. Implementation notes: * Loads sys.dm_exec_connections joined to sys.dm_exec_sessions into a table variable once, then aggregates it three ways so the DMVs are only read once. * Treats sys.dm_exec_sessions.last_request_end_time = 1900-01-01 (the sentinel for sessions that have never run a request) as NULL so it renders as "unknown" rather than "45000 days ago". * Pre-computes the total connection count into a variable and gates the breakdown work behind an explicit IF so we only do the extra aggregation when the alert actually fires. * Bumps sp_BlitzFirst version to 8.33 / 20260415. Closes #3903. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Version and VersionDate on line 50 are maintained by the build process; hand-edits there get overwritten. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR enhances sp_BlitzFirst CheckID 49 (“High Number of Connections”) so that when the alert triggers, the Details output includes a Top-5 breakdown of connection sources (by host, login, and application) with recency/age context to help operators quickly identify where connection pressure is concentrated.
Changes:
- Computes total connection count first and only generates breakdown details when the alert threshold is exceeded.
- Captures connection/session attributes into a single in-memory structure and aggregates it three ways (Top 5 Servers / Logins / Apps).
- Appends the Top-5 breakdown sections to the existing CheckID 49
Detailstext.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| FOR XML PATH(''), TYPE | ||
| ).value('text()[1]', 'nvarchar(max)'), 1, LEN(@LineFeed), N''); |
There was a problem hiding this comment.
The XML concatenation uses .value('text()[1]', 'nvarchar(max)'), which only returns the first text node (so you’ll only get the first Top-5 row in the output). Use .value('.', 'nvarchar(max)') (or the existing pattern in this proc: .value(N'.[1]', N'NVARCHAR(MAX)')) to return the full concatenated string.
| FOR XML PATH(''), TYPE | ||
| ).value('text()[1]', 'nvarchar(max)'), 1, LEN(@LineFeed), N''); |
There was a problem hiding this comment.
Same issue as above: .value('text()[1]', 'nvarchar(max)') will only return the first concatenated row for Top Logins. Switch to .value('.', 'nvarchar(max)') / .value(N'.[1]', N'NVARCHAR(MAX)') so all Top-5 lines are included.
| FOR XML PATH(''), TYPE | ||
| ).value('text()[1]', 'nvarchar(max)'), 1, LEN(@LineFeed), N''); |
There was a problem hiding this comment.
Same XML extraction issue here: .value('text()[1]', 'nvarchar(max)') returns only the first text node, so Top Apps will likely show only one line. Use .value('.', 'nvarchar(max)') (or .value(N'.[1]', N'NVARCHAR(MAX)')) to return the full concatenation.
| + @LineFeed + @LineFeed + 'Top 5 Servers:' + ISNULL(@TopServers, @LineFeed + '(none)') | ||
| + @LineFeed + @LineFeed + 'Top 5 Logins:' + ISNULL(@TopLogins, @LineFeed + '(none)') | ||
| + @LineFeed + @LineFeed + 'Top 5 Apps:' + ISNULL(@TopApps, @LineFeed + '(none)') |
There was a problem hiding this comment.
@TopServers/@TopLogins/@TopApps are built with STUFF(..., 1, LEN(@LineFeed), N''), so they do not start with a line break. In the final Details string you concatenate 'Top 5 X:' + ISNULL(@TopX, ...) (no @LineFeed after the colon), which will render the first entry on the same line as the header. Add a line break after each header (or keep the leading line break in @TopX).
| + @LineFeed + @LineFeed + 'Top 5 Servers:' + ISNULL(@TopServers, @LineFeed + '(none)') | |
| + @LineFeed + @LineFeed + 'Top 5 Logins:' + ISNULL(@TopLogins, @LineFeed + '(none)') | |
| + @LineFeed + @LineFeed + 'Top 5 Apps:' + ISNULL(@TopApps, @LineFeed + '(none)') | |
| + @LineFeed + @LineFeed + 'Top 5 Servers:' + @LineFeed + ISNULL(@TopServers, '(none)') | |
| + @LineFeed + @LineFeed + 'Top 5 Logins:' + @LineFeed + ISNULL(@TopLogins, '(none)') | |
| + @LineFeed + @LineFeed + 'Top 5 Apps:' + @LineFeed + ISNULL(@TopApps, '(none)') |
… output Two fixes based on Copilot review feedback on PR #3921: * Switch .value('text()[1]', 'nvarchar(max)') to .value(N'.[1]', N'NVARCHAR(MAX)') on all three Top-5 STUFF/FOR XML blocks. text()[1] returns only the first text node; when a program_name contains <, >, or & it gets entity-escaped and breaks the concatenation into multiple text nodes, so only the first row would appear. The new form matches the existing pattern used elsewhere in sp_BlitzFirst (see line 2487). * Insert @linefeed between each "Top 5 X:" header and the first row. The Top-5 strings are built with STUFF(..., 1, LEN(@linefeed), N'') so they don't start with a line break, which was running the first entry onto the header line. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks @copilot. Addressed in
|
Closes #3903.
Summary
When CheckID 49 (High Number of Connections) fires, the
Detailscolumn now includes three ranked sections so operators can see where the connection pressure is concentrated:sys.dm_exec_sessions.host_namelogin_nameprogram_nameEach line shows the connection count plus the most-recent and oldest query-finish ages for that group:
Implementation notes
TABLEvariable that's aggregated three different ways. No correlated triple-scan.@TotalConnectionsfirst; the breakdown work only runs when@TotalConnections > @max_worker_threads. No extra cost on healthy servers.sys.dm_exec_sessions.last_request_end_timedefaults to1900-01-01for sessions that have never run a request. That getsNULLIF'd so the row renders as "unknown" rather than "45000 days ago".host_name,login_name,program_namecollapse into(unknown host)/(unknown login)/(unknown app)so internal or anonymous connections don't drop out of the count..value(N'.[1]', N'NVARCHAR(MAX)')— the same pattern already used elsewhere in sp_BlitzFirst — soprogram_namevalues containing<,>, or&don't truncate the output.ORDER BY ConnectionCount DESC, ConnectionGroup— no ties cause random row shuffling.Alternative to this PR: #3904 (same feature, different approach). This version avoids the scalar-aggregate + CROSS APPLY compile issue flagged in review there and collapses the age-formatting CASE from 12 copies to 6.
Test plan
@max_worker_threadsconnections, confirm the check is silent (no row in#BlitzFirstResults).ostress -n3000or a load generator against a VM with low core count so@max_worker_threadsis low), confirm the alert fires and the three Top-5 sections populate.program_namevalues — confirm counts sum correctly to the total.program_namecontains<,>, or&still appears in the Top 5 Apps list (exercises the.[1]XPath fix).🤖 Generated with Claude Code